/*
 * Copyright (C) 2011, EADS France
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 */
package xowl.gmi.view.diagram;

import java.util.List;
import xowl.gmi.environment.Locale;

/**
 *
 * @author Laurent WOUTERS
 */
public class GraphConnector extends SelectableWidget {
    private static class End {
        public GraphNode node;
        public int portID;
        public Couple point;
        public Couple normal;
        public GraphConnectorEnd end;
        public End(GraphConnectorEnd e) { end = e; }
    }
    
    public static interface Actions extends SelectableWidget.Actions {
        void onMenuStyleLine();
        void onMenuStylePolyline();
        void onMenuStyleCurve();
    }
    
    private Actions cActions;
    private End origin;
    private End target;
    private java.awt.Color linkColor;
    private int linkThickness;
    private GraphConnectorStyle cStyle;
    
    public GraphNode getFrom() { return origin.node; }
    public GraphNode getTo() { return target.node; }
    
    public GraphConnector(Actions actions, Locale locale,
                            GraphConnectorEnd origin, GraphConnectorEnd target,
                            java.awt.Color linkColor, int linkThickness) {
        super(actions, locale);
        this.cActions = actions;
        this.origin = new End(origin);
        this.target = new End(target);
        this.linkColor = linkColor;
        this.linkThickness = linkThickness;
        this.cStyle = GraphConnectorStyle.Line;
        
        javax.swing.JMenuItem itemLine = new javax.swing.JMenuItem(locale.diagConnectorLine, xowl.gmi.view.IconProvider.getEmbedded(locale.diagConnectorLine_icon));
        itemLine.addMouseListener(new java.awt.event.MouseAdapter() {
            @Override public void mouseReleased(java.awt.event.MouseEvent evt) { cActions.onMenuStyleLine(); }
        });
        javax.swing.JMenuItem itemPolyline = new javax.swing.JMenuItem(locale.diagConnectorPolyline, xowl.gmi.view.IconProvider.getEmbedded(locale.diagConnectorPolyline_icon));
        itemPolyline.addMouseListener(new java.awt.event.MouseAdapter() {
            @Override public void mouseReleased(java.awt.event.MouseEvent evt) { cActions.onMenuStylePolyline(); }
        });
        javax.swing.JMenuItem itemCurve = new javax.swing.JMenuItem(locale.diagConnectorCurve, xowl.gmi.view.IconProvider.getEmbedded(locale.diagConnectorCurve_icon));
        itemCurve.addMouseListener(new java.awt.event.MouseAdapter() {
            @Override public void mouseReleased(java.awt.event.MouseEvent evt) { cActions.onMenuStyleCurve(); }
        });
        javax.swing.JMenu menuStyle = new javax.swing.JMenu(locale.diagConnectorStyle);
        menuStyle.setIcon(xowl.gmi.view.IconProvider.getEmbedded(locale.diagConnectorStyle_icon));
        menuStyle.add(itemLine);
        menuStyle.add(itemPolyline);
        menuStyle.add(itemCurve);
        this.menu.add(new javax.swing.JSeparator());
        this.menu.add(menuStyle);
    }
    
    public void setBorderColor(java.awt.Color color) {
        this.linkColor = color;
        this.origin.end.setBorderColor(color);
        this.target.end.setBorderColor(color);
    }
    
    public void setBorderThickness(int value) {
        this.linkThickness = value;
        this.origin.end.setBorderThickness(value);
        this.target.end.setBorderThickness(value);
    }
    
    public void setNodes(GraphNode from, GraphNode to) {
        this.origin.node = from;
        this.target.node = to;
    }
    
    public void setPorts(int fromPID, int toPID) {
        this.origin.portID = fromPID;
        this.target.portID = toPID;
        resetBounds();
    }
    
    public void setStyle(GraphConnectorStyle style) { this.cStyle = style; }
    
    public void resetBounds() {
        Hull originHull = origin.node.getHull();
        Hull targetHull = target.node.getHull();
        origin.point = origin.node.position.plus(originHull.getPorts().get(origin.portID));
        origin.normal = originHull.getNormals().get(origin.portID);
        target.point = target.node.position.plus(targetHull.getPorts().get(target.portID));
        target.normal = targetHull.getNormals().get(target.portID);
        position = new Couple(java.lang.Math.min(origin.point.x, target.point.x), java.lang.Math.min(origin.point.y, target.point.y));
        dimension = new Couple(java.lang.Math.abs(origin.point.x - target.point.x), java.lang.Math.abs(origin.point.x - target.point.y));
    }
    
    @Override public boolean contains(Couple input) {
        switch (cStyle) {
            case Line: return contains_line(input);
            case Polyline: return contains_polyline(input);
            case Curve: return contains_curve(input);
        }
        return false;
    }
    private boolean contains_line(Couple input) {
        double ft2 = (new Couple(origin.point, target.point)).length2();
        double pf2 = (new Couple(input, origin.point)).length2();
        double pt2 = (new Couple(input, target.point)).length2();
        double ft = java.lang.Math.sqrt(ft2);
        double max = (5*5 + ft2);
        if (pf2 > max || pt2 > max)
            return false;
        double temp = ((ft2+pf2-pt2)/(2*ft));
        double hp = java.lang.Math.sqrt(pf2 - temp*temp);
        return (hp <= 5);
    }
    private boolean contains_polyline(Couple input) { return false; }
    private boolean contains_curve(Couple input) { return false; }
    
    @Override protected boolean buildStackAt(List<Widget> stack, Couple input) {
        boolean result = false;
        if (contains(input)) {
            stack.add(this);
            result = true;
        }
        if (sub != null && sub.buildStackAt(stack, input))
            result = true;
        return result;
    }
    
    @Override public Couple layout() {
        if (sub == null)
            return dimension;
        sub.layout();
        switch (cStyle) {
            case Line: layout_line(); break;
            case Polyline: layout_polyline(); break;
            case Curve: layout_curve(); break;
        }
        return dimension;
    }
    private void layout_line() {
        Couple dim = sub.getSize();
        Couple v = new Couple(origin.point, target.point);
        Couple offset = v.orthogonal(dim.y * 0.7f);
        Couple m = Couple.middle(origin.point, target.point).plus(offset);
        Couple p = m.minus(dim.mult(0.5d));
        sub.setPosition(p);
    }
    private void layout_polyline() { }
    private void layout_curve() { }
    
    @Override public void paint(java.awt.Graphics2D g2d, float alpha, DiagramStyle style, Couple offset) {
        switch (cStyle) {
            case Line:
                if (isSelected || isHovered)
                    paint_background_line(g2d, style, offset);
                paint_connector_line(g2d, style, offset);
                break;
            case Polyline:
                if (isSelected || isHovered)
                    paint_background_polyline(g2d, style, offset);
                paint_connector_polyline(g2d, style, offset);
                break;
            case Curve:
                if (isSelected || isHovered)
                    paint_background_curve(g2d, style, offset);
                paint_connector_curve(g2d, style, offset);
                break;
        }
        if (sub != null)
            sub.paint(g2d, alpha, style, offset);
    }
    private void paint_background_line(java.awt.Graphics2D g2d, DiagramStyle style, Couple offset) {
        Couple vo = (new Couple(origin.point, target.point)).orthogonal(style.selectablePadSize);
        int[] xs = new int[] {
            (int)(offset.x + origin.point.x + vo.x),
            (int)(offset.x + origin.point.x - vo.x),
            (int)(offset.x + target.point.x - vo.x),
            (int)(offset.x + target.point.x + vo.x)
        };
        int[] ys = new int[] {
            (int)(offset.y + origin.point.y + vo.y),
            (int)(offset.y + origin.point.y - vo.y),
            (int)(offset.y + target.point.y - vo.y),
            (int)(offset.y + target.point.y + vo.y)
        };
        if (isSelected) g2d.setColor(style.selectableSelectedColor);
        else g2d.setColor(style.selectableHoverColor);
        g2d.fillPolygon(xs, ys, 4);
    }
    private void paint_connector_line(java.awt.Graphics2D g2d, DiagramStyle style, Couple offset) {
        Couple newFL = origin.end.paint(g2d, offset, target.point, origin.point, style);
        Couple newTL = target.end.paint(g2d, offset, origin.point, target.point, style);
        newFL = offset.plus(newFL);
        newTL = offset.plus(newTL);
        
        if (linkColor != null) g2d.setColor(linkColor);
        else g2d.setColor(style.connectorColor);
        if (linkThickness != 0) g2d.setStroke(new java.awt.BasicStroke(linkThickness));
        else g2d.setStroke(new java.awt.BasicStroke(style.connectorThickness));
        g2d.drawLine((int)newFL.x, (int)newFL.y, (int)newTL.x, (int)newTL.y);
    }
    private void paint_background_polyline(java.awt.Graphics2D g2d, DiagramStyle style, Couple offset) { }
    private void paint_connector_polyline(java.awt.Graphics2D g2d, DiagramStyle style, Couple offset) { }
    private void paint_background_curve(java.awt.Graphics2D g2d, DiagramStyle style, Couple offset) { }
    private void paint_connector_curve(java.awt.Graphics2D g2d, DiagramStyle style, Couple offset) { }
    
    @Override protected void exportSVG(org.w3c.dom.Document doc, org.w3c.dom.Element parent, float alpha, DiagramStyle style, Couple offset) {
        switch (cStyle) {
            case Line:
                exportSVG_line(doc, parent, alpha, style, offset);
                break;
            case Polyline:
                exportSVG_polyline(doc, parent, alpha, style, offset);
                break;
            case Curve:
                exportSVG_curve(doc, parent, alpha, style, offset);
                break;
        }
        if (sub != null)
            sub.exportSVG(doc, parent, alpha, style, offset);
    }
    private void exportSVG_line(org.w3c.dom.Document doc, org.w3c.dom.Element parent, float alpha, DiagramStyle style, Couple offset) {
        Couple newFL = origin.end.exportSVG(doc, parent, offset, target.point, origin.point, style);
        Couple newTL = target.end.exportSVG(doc, parent, offset, origin.point, target.point, style);
        newFL = offset.plus(newFL);
        newTL = offset.plus(newTL);
        
        org.w3c.dom.Element elem = doc.createElement("line");
        elem.setAttribute("x1", Double.toString(newFL.x));
        elem.setAttribute("y1", Double.toString(newFL.y));
        elem.setAttribute("x2", Double.toString(newTL.x));
        elem.setAttribute("y2", Double.toString(newTL.y));
        java.awt.Color border = (linkColor!=null)? linkColor: style.connectorColor;
        int stroke = (linkThickness!=0)? linkThickness: style.connectorThickness;
        if (stroke > 0 && border != null && border.getAlpha() != 0) {
            elem.setAttribute("stroke", "#" + Widget.getColorString(border));
            if (border.getAlpha() != 255) {
                float value = ((float)border.getAlpha()) / 255;
                elem.setAttribute("stroke-opacity", Float.toString(value));
            }
            elem.setAttribute("stroke-width", Integer.toString(stroke));
        }
        parent.appendChild(elem);
    }
    private void exportSVG_polyline(org.w3c.dom.Document doc, org.w3c.dom.Element parent, float alpha, DiagramStyle style, Couple offset) { }
    private void exportSVG_curve(org.w3c.dom.Document doc, org.w3c.dom.Element parent, float alpha, DiagramStyle style, Couple offset) { }
}
